package handler

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/ipipdotnet/ipdb-go"
	"github.com/spf13/cast"
	"net/url"
	"regexp"
	"shop/internal/global"
	"shop/internal/lock"
	customMiddle "shop/internal/middleware"
	"shop/internal/tools"
	"shop/internal/types"

	"strings"
	"time"

	"context"
	"github.com/zhufuyi/sponge/pkg/gin/middleware"
	"github.com/zhufuyi/sponge/pkg/gin/response"
	"github.com/zhufuyi/sponge/pkg/logger"
	"github.com/zhufuyi/sponge/pkg/mysql/query"
	"github.com/zhufuyi/sponge/pkg/utils"
	"shop/internal/cache"
	"shop/internal/dao"
	"shop/internal/ecode"
	"shop/internal/model"

	"github.com/gin-gonic/gin"
	"github.com/jinzhu/copier"
)

var _ TkAdHandler = (*tkAdHandler)(nil)

// TkAdHandler defining the handler interface
type TkAdHandler interface {
	Create(c *gin.Context)
	DeleteByID(c *gin.Context)
	DeleteByIDs(c *gin.Context)
	UpdateByID(c *gin.Context)
	GetByID(c *gin.Context)
	ListByIDs(c *gin.Context)
	List(c *gin.Context)
	Async(c *gin.Context)
}
type tkAdHandler struct {
	iDao             dao.TkAdDao
	GeneralizeDao    dao.TkGeneralizeDao
	AppletJumpLogDao dao.TkAppletJumpLogDao
	VisitLogDao      dao.TkVisitLogDao
}

// NewTkAdHandler creating the handler interface
func NewTkAdHandler() TkAdHandler {
	return &tkAdHandler{
		iDao: dao.NewTkAdDao(
			model.GetDB(),
			cache.NewTkAdCache(model.GetCacheType()),
		),
		GeneralizeDao: dao.NewTkGeneralizeDao(
			model.GetDB(),
			cache.NewTkGeneralizeCache(model.GetCacheType()),
		),
		AppletJumpLogDao: dao.NewTkAppletJumpLogDao(
			model.GetDB(),
			cache.NewTkAppletJumpLogCache(model.GetCacheType()),
		),
		VisitLogDao: dao.NewTkVisitLogDao(
			model.GetDB(),
			cache.NewTkVisitLogCache(model.GetCacheType()),
		),
	}
}

// Async 绑定广告信息
// @Summary 绑定广告信息
// @Description 绑定广告信息
// @Tags tkAd
// @accept json
// @Produce json
// @Param data body types.AsyncAdReq true "tkAd information"
// @Success 200 {object} types.AsyncAdReply{}
// @Router /ad/async [post]
func (h *tkAdHandler) Async(c *gin.Context) {
	form := &types.AsyncAdReq{}
	err := c.ShouldBindJSON(form)
	if err != nil {
		logger.Warn("ShouldBindJSON error: ", logger.Err(err), middleware.GCtxRequestIDField(c))
		response.Error(c, ecode.InvalidParams.WithDetails(err.Error()))
		return
	}
	ctx := c.Request.Context()
	adInfo, err := h.iDao.GetByCode(ctx, form.AdSign)
	if err != nil && err != query.ErrNotFound {
		logger.Error("query", logger.Err(err), logger.Any("form", form), middleware.GCtxRequestIDField(c))
		response.Output(c, ecode.InternalServerError.ToHTTPCode())
		return
	}
	if adInfo == nil {
		response.Error(c, ecode.NotFound.WithDetails("没有获取到广告信息"))
		return
	}
	//处理source
	source := h.handleUtmSource(ctx, form, adInfo)

	ip := tools.GetClientIP(c)
	ua := c.Request.Header.Get("User-Agent")
	city := h.getCity(ctx, ip)
	requestPath := c.Request.URL.Path
	if c.Request.URL.RawQuery != "" {
		rawQuery, _ := url.PathUnescape(c.Request.URL.RawQuery)
		requestPath = requestPath + "?" + rawQuery
	}
	now := cast.ToInt(time.Now().Unix())

	log := &model.TkVisitLog{
		IP:          ip,
		Sign:        form.AdSign,
		Url:         requestPath,
		StytemType:  "",
		Stytem:      "",
		Browser:     "",
		Ua:          ua,
		ClickID:     source.Uid,
		DeviceInfo:  "",
		Time:        0,
		ProductID:   adInfo.ProductID,
		SourceUrl:   "",
		JumpUrl:     "",
		VisitUrl:    "",
		City:        "",
		DomainName:  "",
		AdID:        0,
		AdAccountID: 0,
		BPageType:   0,
		CreatedAt:   now,
		UpdatedAt:   now,
	}
	err = h.VisitLogDao.Create(ctx, log)
	if err != nil {
		logger.Error("create VisitLogDao", logger.Err(err), logger.Any("data", log), middleware.GCtxRequestIDField(c))
		response.Output(c, ecode.InternalServerError.ToHTTPCode())
	}
	logId := log.ID
	jsonLog, _ := json.Marshal(log)
	lock.GetInstance().Client.SetEX(ctx, "visit_log_"+cast.ToString(logId), string(jsonLog), 900)
	jumpInfo := make(map[string]interface{})
	jumpInfo["log_id"] = logId
	jumpInfo["t"] = form.AdSign
	jsonJump, _ := json.Marshal(jumpInfo)
	bindIdInt, _ := tools.GetUniqueInstance().NextID()
	bindId := cast.ToString(bindIdInt)
	jumpModel := &model.TkAppletJumpLog{
		BindID:    "",
		Appid:     form.Appid,
		Data:      string(jsonJump),
		CreatedAt: now,
	}
	err = h.AppletJumpLogDao.Create(ctx, jumpModel)
	if err != nil {
		logger.Error("create AppletJumpLogDao", logger.Err(err), logger.Any("data", jumpModel), middleware.GCtxRequestIDField(c))
		response.Output(c, ecode.InternalServerError.ToHTTPCode())
	}
	t := time.Now()
	generalize := &model.TkGeneralize{
		AdID:           adInfo.ID,
		AdAccountID:    adInfo.AdAccountID,
		TripartiteAdID: source.Aid,
		ClickID:        source.Uid,
		StytemType:     form.SystemType,
		ProductID:      adInfo.ProductID,
		PromoteID:      0,
		ChannelID:      "",
		ChannelType:    adInfo.PlatformID,
		CallbackNode:   adInfo.CallbackStatus,
		DeviceInfo:     form.DeviceInfo,
		IP:             ip,
		City:           city,
		Url:            requestPath,
		JumpUrl:        "",
		AppID:          form.Appid,
		Status:         1,
		CreatedAt:      &t,
		UpdatedAt:      &t,
		LogID:          0,
		Ua:             ua,
		ShopID:         adInfo.ShopID,
		AdminID:        adInfo.AdminID,
		CompanyID:      adInfo.CompanyID,
	}

	err = h.GeneralizeDao.Create(ctx, generalize)
	if err != nil {
		logger.Error("create GeneralizeDao", logger.Err(err), logger.Any("data", generalize), middleware.GCtxRequestIDField(c))
		response.Output(c, ecode.InternalServerError.ToHTTPCode())
	}

	logInfo := map[string]interface{}{
		"log_id": logId,
		"t":      form.AdSign,
	}
	jsonLogInfo, _ := json.Marshal(logInfo)
	logUniqueId := fmt.Sprintf(global.RedisOrderBindInfo, cast.ToString(bindId))
	lock.GetInstance().Client.SetEX(ctx, logUniqueId, string(jsonLogInfo), 43200)
	//判断c_type 1：A流量，2：B流量
	cType := int64(2)
	if checkRes, _ := h.getCheckRes(form, ip); !checkRes {
		cType = 1
	}
	if jump := h.checkScene(form.Scene, cType, city, ua); jump {
		cType = 1
	}
	//缓存uxinfo和ctype
	uxinfoKey := fmt.Sprintf("uxinfo:{%s}", bindId)
	ctypeKey := fmt.Sprintf("ctype:{%s}", bindId)
	lock.GetInstance().Client.SetEX(ctx, uxinfoKey, form.Uxinfo, 300)
	lock.GetInstance().Client.SetEX(ctx, ctypeKey, cast.ToString(cType), 300)

	resp := &types.AsyncAdReply{}
	resp.BindId = cast.ToString(bindId)
	resp.CType = cType

	response.Success(c, resp)
}

func GetBaseWhere(c *gin.Context) *customMiddle.CustBaseWhere {
	get, exists := c.Get("baseWhere")
	if exists {
		where := get.(*customMiddle.CustBaseWhere)
		return where
	}
	return nil
}

func (h *tkAdHandler) checkScene(scene int64, cType int64, city string, ua string) bool {
	cities := []string{"上海", "北京", "广州", "深圳", "成都"}
	scenes := []int64{1001, 1007, 1008, 1010, 1011, 1012, 1013, 1047, 1048, 1069, 1089, 1131, 1232, 1023, 1187}
	var err error
	citys := strings.Split(city, "-")
	lastCity := citys[len(citys)-1]
	isCity := contains(cities, lastCity)
	if (cType == 1 || isCity) && scene == 1228 {
		err = errors.New("视频号版位问题")
	}
	res := containsInt(scenes, scene)
	if res {
		err = errors.New("异常版位问题")
	}

	if city == "云服务" {
		err = errors.New("云服务")
	}

	matched, _ := regexp.MatchString(`(iPhone|Mac)[ ]OS[ ](X)?[ ]?12[\_]`, ua)
	if matched && lastCity == "深圳" {
		err = errors.New("iphone12问题")
	}
	if err != nil {
		return true
	}
	return false
}

func contains(slice []string, item string) bool {
	for _, a := range slice {
		if a == item {
			return true
		}
	}
	return false
}

func containsInt(slice []int64, item int64) bool {
	for _, a := range slice {
		if a == item {
			return true
		}
	}
	return false
}

func (h *tkAdHandler) getCheckRes(req *types.AsyncAdReq, ip string) (bool, error) {
	checkRes := true
	if req.Uxinfo != "" {
		uxinfoDecoded := req.Uxinfo + "|ip:" + ip + "|sign:" + req.AdSign
		if strings.Contains(uxinfoDecoded, "AgJ4jIrRWuwm+moXGoK9+vOLPn3qP75bfsmJSl21CS7TABK51FQcuqlDp0tiu4+nur9Sb8XCX5RffXHc6ObahJG8ozy4/b6Ii7ZF5g==") {
			checkRes = false
			return checkRes, nil
		} else {
			return checkRes, nil
		}
	} else {
		return checkRes, nil
	}
}

func (h *tkAdHandler) getCity(ctx context.Context, ip string) string {
	city := lock.GetInstance().Client.Get(ctx, ip).String()
	if city == "" {
		reqUrl := "https://unpkg.com/qqwry.ipdb/qqwry.ipdb"
		cityPath := "ipdb.ipdb"
		if err := tools.DownloadFile(reqUrl, cityPath); err != nil {
			panic(err)
		}

		ipDb, err := ipdb.NewCity(cityPath)
		if err != nil {
			panic(err)
		}
		info, err := ipDb.FindInfo(ip, "CN")
		if err != nil {
			panic(err)
		}
		city = fmt.Sprintf("%s-%s", info.RegionName, info.CityName)
		lock.GetInstance().Client.SetEX(ctx, ip, city, 86400)
	}
	return city
}

func (h *tkAdHandler) handleUtmSource(ctx context.Context, req *types.AsyncAdReq, adInfo *model.TkAd) *types.ResInfo {
UtmSource:
	reqUtmSource := make(map[string]string)
	for _, part := range strings.Split(req.UtmSource, "&") {
		partInfo := strings.Split(part, "=")
		if len(partInfo) != 2 {
			continue
		}
		reqUtmSource[partInfo[0]] = partInfo[1]
	}
	var source *types.ResInfo
	switch reqUtmSource["source"] {
	case "magnetism":
		source = &types.ResInfo{
			Uid:    reqUtmSource["clickid"],
			Aid:    reqUtmSource["ad_id"],
			OpenId: "",
			Vid:    "",
			AdType: "",
		}
	case "weixin":
		resInfo := &types.ResInfo{}
		if gdtAdId, ok := reqUtmSource["gdt_ad_id"]; ok && gdtAdId != "" {
			resInfo.Aid = gdtAdId
		} else if wxAid, wxOk := reqUtmSource["wx_aid"]; wxOk && wxAid != "" {
			resInfo.Aid = wxAid
			resInfo.AdType = "wx"
		} else if weiXinAdInfo, wxXinAdOk := reqUtmSource["weixinadinfo"]; wxXinAdOk && weiXinAdInfo != "" {
			dotIndex := strings.Index(weiXinAdInfo, ".")
			resInfo.Aid = weiXinAdInfo[:dotIndex]
			resInfo.AdType = "wx"
		}

		if gdtVid, ok := reqUtmSource["gdt_vid"]; ok && gdtVid != "" {
			resInfo.Uid = gdtVid
		} else if qzGdt, qzGdtOk := reqUtmSource["qz_gdt"]; qzGdtOk && qzGdt != "" {
			resInfo.Uid = qzGdt
		} else if wxTraceId, wxTraceIdOk := reqUtmSource["wx_traceid"]; wxTraceIdOk && wxTraceId != "" {
			resInfo.Uid = wxTraceId
		}

		if openId, ok := reqUtmSource["open_id"]; ok && openId != "" {
			resInfo.OpenId = openId
		}
		source = resInfo
	default:
		source = &types.ResInfo{}
		// 没有传,查询是否磁力，如果是获取req_id
		if adInfo.PlatformID == cast.ToInt(global.AdChannelKs) {
			reqId, ok := reqUtmSource["req_id"]
			if ok {
				key := fmt.Sprintf(global.RedisMiniShortUrlParams, cast.ToInt64(reqId))
				value := lock.GetInstance().Client.Get(ctx, key)
				req.UtmSource = cast.ToString(value)
				req.UtmSource = strings.ReplaceAll(req.UtmSource, "$", "&")
				goto UtmSource
			}
		}
	}

	return source
}

// Create a record
// @Summary create tkAd
// @Description submit information to create tkAd
// @Tags tkAd
// @accept json
// @Produce json
// @Param data body types.CreateTkAdRequest true "tkAd information"
// @Success 200 {object} types.Result{}
// @Router /api/v1/tkAd [post]
func (h *tkAdHandler) Create(c *gin.Context) {
	form := &types.CreateTkAdRequest{}
	err := c.ShouldBindJSON(form)
	if err != nil {
		logger.Warn("ShouldBindJSON error: ", logger.Err(err), middleware.GCtxRequestIDField(c))
		response.Error(c, ecode.InvalidParams)
		return
	}

	tkAd := &model.TkAd{}
	err = copier.Copy(tkAd, form)
	if err != nil {
		response.Error(c, ecode.ErrCreateTkAd)
		return
	}

	err = h.iDao.Create(c.Request.Context(), tkAd)
	if err != nil {
		logger.Error("Create error", logger.Err(err), logger.Any("form", form), middleware.GCtxRequestIDField(c))
		response.Output(c, ecode.InternalServerError.ToHTTPCode())
		return
	}

	response.Success(c, gin.H{"id": tkAd.ID})
}

// DeleteByID delete a record by id
// @Summary delete tkAd
// @Description delete tkAd by id
// @Tags tkAd
// @accept json
// @Produce json
// @Param id path string true "id"
// @Success 200 {object} types.Result{}
// @Router /api/v1/tkAd/{id} [delete]
func (h *tkAdHandler) DeleteByID(c *gin.Context) {
	_, id, isAbort := getTkAdIDFromPath(c)
	if isAbort {
		return
	}

	err := h.iDao.DeleteByID(c.Request.Context(), id)
	if err != nil {
		logger.Error("DeleteByID error", logger.Err(err), logger.Any("id", id), middleware.GCtxRequestIDField(c))
		response.Output(c, ecode.InternalServerError.ToHTTPCode())
		return
	}

	response.Success(c)
}

// DeleteByIDs delete records by batch id
// @Summary delete tkAds
// @Description delete tkAds by batch id
// @Tags tkAd
// @Param data body types.DeleteTkAdsByIDsRequest true "id array"
// @Accept json
// @Produce json
// @Success 200 {object} types.Result{}
// @Router /api/v1/tkAd/delete/ids [post]
func (h *tkAdHandler) DeleteByIDs(c *gin.Context) {
	form := &types.DeleteTkAdsByIDsRequest{}
	err := c.ShouldBindJSON(form)
	if err != nil {
		logger.Warn("ShouldBindJSON error: ", logger.Err(err), middleware.GCtxRequestIDField(c))
		response.Error(c, ecode.InvalidParams)
		return
	}

	err = h.iDao.DeleteByIDs(c.Request.Context(), form.IDs)
	if err != nil {
		logger.Error("GetByIDs error", logger.Err(err), logger.Any("form", form), middleware.GCtxRequestIDField(c))
		response.Output(c, ecode.InternalServerError.ToHTTPCode())
		return
	}

	response.Success(c)
}

// UpdateByID update information by id
// @Summary update tkAd
// @Description update tkAd information by id
// @Tags tkAd
// @accept json
// @Produce json
// @Param id path string true "id"
// @Param data body types.UpdateTkAdByIDRequest true "tkAd information"
// @Success 200 {object} types.Result{}
// @Router /api/v1/tkAd/{id} [put]
func (h *tkAdHandler) UpdateByID(c *gin.Context) {
	_, id, isAbort := getTkAdIDFromPath(c)
	if isAbort {
		return
	}

	form := &types.UpdateTkAdByIDRequest{}
	err := c.ShouldBindJSON(form)
	if err != nil {
		logger.Warn("ShouldBindJSON error: ", logger.Err(err), middleware.GCtxRequestIDField(c))
		response.Error(c, ecode.InvalidParams)
		return
	}
	form.ID = id

	tkAd := &model.TkAd{}
	err = copier.Copy(tkAd, form)
	if err != nil {
		response.Error(c, ecode.ErrUpdateByIDTkAd)
		return
	}

	err = h.iDao.UpdateByID(c.Request.Context(), tkAd)
	if err != nil {
		logger.Error("UpdateByID error", logger.Err(err), logger.Any("form", form), middleware.GCtxRequestIDField(c))
		response.Output(c, ecode.InternalServerError.ToHTTPCode())
		return
	}

	response.Success(c)
}

// GetByID get a record by id
// @Summary get tkAd detail
// @Description get tkAd detail by id
// @Tags tkAd
// @Param id path string true "id"
// @Accept json
// @Produce json
// @Success 200 {object} types.Result{}
// @Router /api/v1/tkAd/{id} [get]
func (h *tkAdHandler) GetByID(c *gin.Context) {
	idStr, id, isAbort := getTkAdIDFromPath(c)
	if isAbort {
		return
	}

	tkAd, err := h.iDao.GetByID(c.Request.Context(), id)
	if err != nil {
		if errors.Is(err, query.ErrNotFound) {
			logger.Warn("GetByID not found", logger.Err(err), logger.Any("id", id), middleware.GCtxRequestIDField(c))
			response.Error(c, ecode.NotFound)
		} else {
			logger.Error("GetByID error", logger.Err(err), logger.Any("id", id), middleware.GCtxRequestIDField(c))
			response.Output(c, ecode.InternalServerError.ToHTTPCode())
		}
		return
	}

	data := &types.GetTkAdByIDRespond{}
	err = copier.Copy(data, tkAd)
	if err != nil {
		response.Error(c, ecode.ErrGetByIDTkAd)
		return
	}
	data.ID = idStr

	response.Success(c, gin.H{"tkAd": data})
}

// ListByIDs list of records by batch id
// @Summary list of tkAds by batch id
// @Description list of tkAds by batch id
// @Tags tkAd
// @Param data body types.GetTkAdsByIDsRequest true "id array"
// @Accept json
// @Produce json
// @Success 200 {object} types.Result{}
// @Router /api/v1/tkAd/list/ids [post]
func (h *tkAdHandler) ListByIDs(c *gin.Context) {
	form := &types.GetTkAdsByIDsRequest{}
	err := c.ShouldBindJSON(form)
	if err != nil {
		logger.Warn("ShouldBindJSON error: ", logger.Err(err), middleware.GCtxRequestIDField(c))
		response.Error(c, ecode.InvalidParams)
		return
	}

	tkAdMap, err := h.iDao.GetByIDs(c.Request.Context(), form.IDs)
	if err != nil {
		logger.Error("GetByIDs error", logger.Err(err), logger.Any("form", form), middleware.GCtxRequestIDField(c))
		response.Output(c, ecode.InternalServerError.ToHTTPCode())

		return
	}

	tkAds := []*types.GetTkAdByIDRespond{}
	for _, id := range form.IDs {
		if v, ok := tkAdMap[id]; ok {
			record, err := convertTkAd(v)
			if err != nil {
				response.Error(c, ecode.ErrListTkAd)
				return
			}
			tkAds = append(tkAds, record)
		}
	}

	response.Success(c, gin.H{
		"tkAds": tkAds,
	})
}

// List of records by query parameters
// @Summary list of tkAds by query parameters
// @Description list of tkAds by paging and conditions
// @Tags tkAd
// @accept json
// @Produce json
// @Param data body types.Params true "query parameters"
// @Success 200 {object} types.Result{}
// @Router /api/v1/tkAd/list [post]
func (h *tkAdHandler) List(c *gin.Context) {
	form := &types.GetTkAdsRequest{}
	err := c.ShouldBindJSON(form)
	if err != nil {
		logger.Warn("ShouldBindJSON error: ", logger.Err(err), middleware.GCtxRequestIDField(c))
		response.Error(c, ecode.InvalidParams)
		return
	}

	tkAds, total, err := h.iDao.GetByColumns(c.Request.Context(), &form.Params)
	if err != nil {
		logger.Error("GetByColumns error", logger.Err(err), logger.Any("form", form), middleware.GCtxRequestIDField(c))
		response.Output(c, ecode.InternalServerError.ToHTTPCode())
		return
	}

	data, err := convertTkAds(tkAds)
	if err != nil {
		response.Error(c, ecode.ErrListTkAd)
		return
	}

	response.Success(c, gin.H{
		"tkAds": data,
		"total": total,
	})
}

func getTkAdIDFromPath(c *gin.Context) (string, uint64, bool) {
	idStr := c.Param("id")
	id, err := utils.StrToUint64E(idStr)
	if err != nil || id == 0 {
		logger.Warn("StrToUint64E error: ", logger.String("idStr", idStr), middleware.GCtxRequestIDField(c))
		response.Error(c, ecode.InvalidParams)
		return "", 0, true
	}

	return idStr, id, false
}

func convertTkAd(tkAd *model.TkAd) (*types.GetTkAdByIDRespond, error) {
	data := &types.GetTkAdByIDRespond{}
	err := copier.Copy(data, tkAd)
	if err != nil {
		return nil, err
	}
	data.ID = utils.Uint64ToStr(tkAd.ID)
	return data, nil
}

func convertTkAds(fromValues []*model.TkAd) ([]*types.GetTkAdByIDRespond, error) {
	toValues := []*types.GetTkAdByIDRespond{}
	for _, v := range fromValues {
		data, err := convertTkAd(v)
		if err != nil {
			return nil, err
		}
		toValues = append(toValues, data)
	}

	return toValues, nil
}
